Passed
Pull Request — master (#106)
by Mark
01:39
created

Model.jsonStringify   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
import HasManyThrough from './Model/Relation/HasManyThrough';
2
import HasMany from "./Model/Relation/HasMany";
3
import BelongsTo from "./Model/Relation/BelongTo";
4
import MorphOne from "./Model/Relation/MorphOne";
5
import HasOne from "./Model/Relation/HasOne";
6
import HasOneThrough from "./Model/Relation/HasOneThrough";
7
import MorphTo from "./Model/Field/MorphTo";
8
import Field from "./Model/Field";
9
import Relation from "./Model/Relation";
10
import ForeignKey from "./Model/Field/ForeignKey";
11
import Index from "./Table/Index";
12
import {ModelInterface, ModelStaticInterface} from "../JeloquentInterfaces";
13
import Collection from "./Collection";
14
import * as Str from "../Util/Str";
15
import * as Obj from "../Util/Obj";
16
import * as ModelSetup from "./Model/Util/Setup";
17
import Table from "./Table";
18
19
class Model implements ModelInterface {
20
21
    private static kebabCaseName: string;
22
23
    private static snakeCaseName: string;
24
25
    _tmpId: string;
26
27
    ['constructor']: ModelStaticInterface;
28
29
    private _originalFields: Map<string, Field>;
30
31
    private _primaryFields: Field[];
32
33
    private numberOfFields: number;
34
35
    constructor(fields: Field[] = []) {
36
        this.setFields(ModelSetup.addRelationFieldsToList(fields));
37
        this._tmpId = `_${++globalThis.Store.numberOfModelCreated}`;
38
39
        return ModelSetup.modelProxy(this);
40
    }
41
42
    static get className(): string {
43
        return this.name;
44
    }
45
46
    static get kebabCaseClassName(): string {
47
        return this.kebabCaseName ??= Str.KebabCase(this.className);
48
    }
49
50
    static get snakeCaseClassName(): string {
51
        return this.snakeCaseName ??= Str.SnakeCase(this.className);
52
    }
53
54
    get className(): string {
55
        return this.constructor.className;
56
    }
57
58
    get dirtyFieldNames(): string[] {
59
        return this.dirtyFields.map(field => field.name);
60
    }
61
62
    get dirtyFields(): Field[] {
63
        return this.originalFields.filter(field => field.isDirty);
64
    }
65
66
    get kebabCaseClassName(): string {
67
        return this.constructor.kebabCaseClassName;
68
    }
69
70
    get originalFields(): Field[] {
71
        return [...this._originalFields.values()];
72
    }
73
74
    get originalPrimaryKey(): unknown {
75
        return this.primaryFields.reduce((toValue, field, i) => {
76
            if (i > 0) {
77
                return `${toValue}-${field.originalValue}`;
78
            }
79
            return field.originalValue;
80
        }, '') ?? this._tmpId ?? null;
81
    }
82
83
    get originalValues(): object {
84
        return this.originalFields.reduce((originalValues, field) => {
85
            if (field.originalValue !== undefined) {
86
                originalValues[field.name] = field.originalValue;
87
            }
88
            return originalValues;
89
        }, {});
90
    }
91
92
    get primaryFields(): Field[] {
93
        return this._primaryFields ??= this.originalFields.filter(field => field.isPrimary);
94
    }
95
96
    get primaryKey(): string {
97
        return (this.primaryFields.reduce((toValue:string, field:Field, i:number): string => {
98
            if (i > 0) {
99
                return `${toValue}-${field.value}`;
100
            }
101
102
            if (field.value === null) {
103
                return field.value;
104
            }
105
106
            return `${field.value}`;
107
        }, '') ?? this._tmpId ?? null);
108
    }
109
110
    get primaryKeyName(): string[] {
111
        return this.originalFields.filter(field => field.isPrimary).map(field => field.name);
112
    }
113
114
    get snakeCaseClassName(): string {
115
        return this.constructor.snakeCaseClassName;
116
    }
117
118
    static aSyncInsert(data: object): Promise<Collection> {
119
        return new Promise((resolve) => {
120
            queueMicrotask(() => {
121
                resolve(this.insert(data));
122
            });
123
        });
124
    }
125
126
    static aSyncUpdate(data: object): Promise<Collection> {
127
        return new Promise((resolve) => {
128
            queueMicrotask(() => {
129
                resolve(this.update(data));
130
            });
131
        });
132
    }
133
134
    static all(): Collection {
135
        return globalThis.Store.database().all(this.className);
136
    }
137
138
    static delete(id): void {
139
        globalThis.Store.database().delete(this.className, id);
140
    }
141
142
    static find(id): unknown {
143
        return globalThis.Store.database().find(this.className, id);
144
    }
145
146
    static getIndexByKey(indexName) {
147
        return globalThis
148
            .Store
149
            .database()
150
            .getIndexByKey(this.className, indexName);
151
    }
152
153
    static getInstance(): ModelInterface {
154
        const original = globalThis.Store.classInstances[this.className] ?? (globalThis.Store.classInstances[this.className] = new this())
155
        const fieldsClone = original.originalFields.reduce((obj, field) => {
156
            obj.push(Object.assign(Object.create(Object.getPrototypeOf(field)), field));
157
            return obj;
158
        }, [])
159
160
        return ModelSetup.modelProxy(Object.create(Object.getPrototypeOf(original)).setFields(fieldsClone));
161
    }
162
163
    static ids() {
164
        return globalThis
165
            .Store
166
            .database()
167
            .ids(this.className);
168
    }
169
170
    static insert(data: object|object[]): Collection {
171
        const modelsData = Array.isArray(data) ? data : [data];
172
        const length = modelsData.length;
173
        const models = new Collection();
174
        for (let i = 0; i < length; i++) {
175
            const modelData = modelsData[i];
176
            const model = this.getInstance();
177
            model.fill(modelData);
178
            globalThis.Store.database().insert(this.className, model);
179
            model.fillRelations(modelData);
180
            models.push(model);
181
        }
182
        return models;
183
    }
184
185
    static registerIndex(name: string): void {
186
        Index.register(this.getInstance(), name);
187
    }
188
189
    static update(data: object|object[]): Collection {
190
        const modelsData = Array.isArray(data) ? data : [data];
191
        const length = modelsData.length;
192
        const models = new Collection();
193
        for (let i = 0; i < length; i++) {
194
            const model = this.getInstance();
195
            model.fill(data);
196
            globalThis.Store.database().update(this.className, model);
197
            model.fillRelations(data);
198
            models.push(model);
199
        }
200
        return models;
201
    }
202
203
    delete() {
204
        this.constructor.delete(this.primaryKey);
205
    }
206
207
    fill(data) {
208
        for (let i = 0; i < this.numberOfFields; i++) {
209
            if (!(this.originalFields[i] instanceof Relation)) {
210
                const fieldName = this.originalFields[i].name;
211
                if (data[fieldName] !== undefined) {
212
                    this[`_${fieldName}`] = data[fieldName];
213
                }
214
            }
215
        }
216
    }
217
218
    fillRelations(data: object): void {
219
        // insert through relations after model insert;
220
        for (let i = 0; i < this.numberOfFields; i++) {
221
            if ((this.originalFields[i] instanceof Relation)) {
222
                const fieldName = this.originalFields[i].name;
223
                if (data[fieldName] !== undefined) {
224
                    this[`_${fieldName}`] = data[fieldName];
225
                }
226
            }
227
        }
228
    }
229
230
    isDirty(fieldName:string = null) {
231
        if (fieldName) {
232
            return this.dirtyFieldNames.includes(fieldName);
233
        }
234
        return this.dirtyFields.length > 0;
235
    }
236
237
    registerIndex(name) {
238
        Index.register(this, name);
239
    }
240
241
    resetDirty() {
242
        this.originalFields.filter((field) => !(field instanceof Relation)).forEach(field => {
243
            field.resetDirty();
244
        })
245
    }
246
247
    save() {
248
        globalThis.Store.database().save(this.className, this);
249
    }
250
251
    setFields(fields: Field[]) {
252
        this._originalFields = new Map();
253
        ModelSetup.setFields(this, fields);
254
        return this;
255
    }
256
257
    tableSetup(table: Table) {
258
        ModelSetup.setupTable(this, table);
259
    }
260
261
    toJSON(): object {
262
        return this.toObject();
263
    }
264
265
    toObject(fromRelation = false): object {
266
        return Obj.fromModel(this, fromRelation);
267
    }
268
}
269
270
export {
271
    Model,
272
    Field,
273
    Relation,
274
    BelongsTo,
275
    HasOne,
276
    HasOneThrough,
277
    HasMany,
278
    HasManyThrough,
279
    MorphOne,
280
    MorphTo,
281
    ForeignKey,
282
};